﻿#$language = "Python"
#$interface = "1.0"

'''common function for cRNC RNW PLAN'''

import os
import sys
import time

g_local_path = os.path.dirname(os.path.abspath(__file__))
if g_local_path not in sys.path:
    sys.path.insert(0, g_local_path)

import crnc
import debug
import plan_tool
from const import CONST_JUST_WAIT

# Constants for plan management.
PLAN_TASK_ID_REQ_ACK   = 'D80C'  # plan_task_id_request_ack_s
PLAN_DL_SCHEMA_VERSION = 'D5FA'  # rnw_plan_schema_version_s
PLAN_DL_ACK            = 'D7FD'  # rnw_plan_download_ack_s
PLAN_DL_END_MSG        = 'D7FB'  # rnw_plan_download_end_s
PLAN_ACT_ACK           = 'D7F9'  # rnw_plan_activation_ack_s
PLAN_ACT_END_MSG       = 'D7F8'  # rnw_plan_activation_end_s
PLAN_UL_ACK            = 'D7F6'  # rnw_plan_upload_ack_s
PLAN_UL_END_MSG        = 'D7F5'  # rnw_plan_upload_end_s
PLAN_FEEDBACK_ACK      = 'D7F3'  # rnw_plan_feedback_ack_s
PLAN_FEEDBACK          = 'D7F4'  # rnw_plan_feedback_s
TASK_ID_GLOBAL_DL      = '10,10,10,10'
TASK_ID_GLOBAL_ACT     = '11,11,11,11'
TASK_ID_GLOBAL_UL      = '12,12,12,12'
PLAN_ID_GLOBAL         = '33,33,33,33'
TASK_ID_DUMMY          = '00,00,00,00'
PLAN_ACT_MODE          = '00' # '00'-safe mode, '10'-normal mode, '20'-fast mode

MAX_RETRY_TIMES        = 4
MAX_PLAN_WAIT_TIME     = 180  # plan download/activation/upload timeout

CMD_INTERVAL = 1.0  # second

class cRnwPlan():
    def __init__(self, obj, debug_enable=False):
        self.obj = obj
        self.debug = debug_enable
        self.log = debug.Log('plan: ')
        self.rnc = crnc.cRNC(obj, True)

    def __del__(self):
        del rnc
        del log

    def wait_noraml_db_state(self):
        self.rnc.goto_top()
        self.log.w('check RNW PLAN db state', end=' ')
        db_state = None
        err_msg = None
        for times in range(50):
            debug.sleep(CMD_INTERVAL)
            rspd = self.rnc.main_crt.send('ZDBS:RAQUEL,0:;\r')
            for line in rspd.splitlines():
                if 'RAQUEL' in line and 'WO' in line:
                    db_state = line.split()[3]
                    break
            else:
                continue

            if not db_state:
                err_msg = 'can not find RAQUEL DB status!'
                rslt = False
                break
            elif db_state == 'NORMAL':  # RNW database is in normal state 
                self.log.w('NORMAL', False, end=' ')
                rslt = True
                break
            elif db_state == 'WAITING':
                # RNW database not in normal state, waiting...
                self.log.w('WAITING', False, end=' ')
                # 'Force RNW DB dump with MML...'
                mml = 'ZDBC:RAQUEL,0:SFTY=2,PHASE=START,MODE=BACK,TRANS=PREV;\r'
                self.obj.Screen.Send(mml)
                self.obj.Screen.WaitForString('Y/N ?', 5)
                self.obj.Screen.Send('Y\r')
                self.obj.Screen.WaitForString(CONST_JUST_WAIT, 1)
            debug.sleep(10.0)
        else:
            err_msg = 'time out.'
            rslt = False

        if rslt:
            self.log.w('successful.', False)
        else:
            self.log.w('failed.', False)
            if err_msg is not None:
                self.log.w(err_msg)

        return rslt

    def wait_ack_msg(self, ack_msg, timeout=MAX_PLAN_WAIT_TIME):
        state_msg = None
        err_msg = None
        self.log.w('wait ack message ' + ack_msg, 
            end=' ' if not self.debug else '\n')
        for times in range(timeout):
            debug.sleep(CMD_INTERVAL)
            rspd = self.rnc.board_crt.send('ZOT\r')
            for line in rspd.splitlines():
                if 'RECEIVED MESSAGE:' in line:
                    state_msg = line.split()[9]
                    break
            else:
                continue

            if self.debug:
                ascii_str = []
                for line in rspd.splitlines():
                    if 'RECEIVED MESSAGE:' not in line:
                        ascii_str.append(line)
                self.log.de_ascii(''.join(ascii_str))

            if state_msg == ack_msg:
                rslt = True
                break
            elif state_msg == PLAN_FEEDBACK:
                # unexpected feedback message received, skip...
                continue
            else:
                err_msg = 'unexpected message {0} received.'.format(state_msg)
                rslt = False
                break
        else:
            err_msg = 'time out.'
            rslt = False

        if rslt:
            self.log.w('successful.', False if not self.debug else True)
        else:
            self.log.w('failed.', False if not self.debug else True)
            if err_msg is not None:
                self.log.w(err_msg)

        return rslt

    def check_operate_result(self, end_msg):
        state_msg = None
        err_msg = None
        trace_err = False
        self.log.w('check opreate result ' + end_msg, 
            end=' ' if not self.debug else '\n')
        for times in range(MAX_PLAN_WAIT_TIME):
            debug.sleep(CMD_INTERVAL)
            rspd = self.rnc.board_crt.send('ZOT\r')
            lines = rspd.splitlines()
            line_num = len(lines)
            for i in range(line_num):
                if 'RECEIVED MESSAGE:' in lines[i]:
                    state_msg = lines[i].split()[9]
                    break
            else:
                continue

            if self.debug:
                ascii_str = []
                for line in rspd.splitlines():
                    if 'RECEIVED MESSAGE:' not in line:
                        ascii_str.append(line)
                self.log.de_ascii(''.join(ascii_str))

            if state_msg == PLAN_DL_END_MSG or state_msg == PLAN_ACT_END_MSG:
                if line_num > i+1:
                    state = lines[i+1][:11]
                else:
                    continue
            elif state_msg == PLAN_UL_END_MSG:
                if line_num > i+3:
                    state = lines[i+3][54:65]
                else:
                    continue
            elif state_msg == PLAN_FEEDBACK \
                or state_msg == PLAN_FEEDBACK_ACK \
                or state_msg == PLAN_SCHEMA_VERSION \
                or state_msg == PLAN_DL_ACK:
                continue
            else:
                err_msg = 'unexpected message {0} received.'.format(state_msg)
                rslt = False
                break

            if state != '00 00 00 00':
                err_msg = 'error state "{0}" detected.'.format(state)
                trace_err = True
                rslt = False
                break
            else:
                rslt = True  # Plan operation finished.
                break
        else:
            err_msg = 'time out.'
            rslt = False

        if rslt:
            self.log.w('successful.', False if not self.debug else True)
        else:
            self.log.w('failed.', False if not self.debug else True)
            if err_msg is not None:
                self.log.w(err_msg)
            if trace_err:
                self.rnc.trace_omu_err()

        return rslt

    def upload_plan(self):
        self.log.w('start upload plan.')
        self.rnc.clear_omu_buffer()
        if not self.wait_noraml_db_state():
            return False

        self.rnc.enter_omu()
        # upload plan downloadable
        mml = 'ZOS:*,*,524,,,,,FACC,,,00,BC,00,00,00,00\r'
        self.rnc.board_crt.send(mml)

        # Send task_id for plan upload (message plan_task_id_request_s)
        mml = 'ZOS:*,*,523,,,,,D80D,,,12,{0},00,00,00,00,{1}\r'.format(
                TASK_ID_GLOBAL_UL, TASK_ID_DUMMY)
        self.rnc.board_crt.send(mml)
        for times in range(MAX_RETRY_TIMES):
            if self.wait_ack_msg(PLAN_TASK_ID_REQ_ACK):
                break
            else:
                debug.sleep(10.0*(times+1))
        else:
            self.log.w('set plan task id failed.')
            return False

        # D7F7 is 'rnw_plan_upload_req_s' NEMU --> RAYBAN
        # Starts the RNW plan (configuration) upload operation in RNC DX.
        mml = 'ZOS:*,*,523,,,,,D7F7,,,{0},,,,,,,,\r'.format(TASK_ID_GLOBAL_UL)
        self.rnc.board_crt.send(mml)
        if not self.wait_ack_msg(PLAN_UL_ACK):
            self.log.w('wait paln upload act failed.')
            return False

        if not self.check_operate_result(PLAN_UL_END_MSG):
            self.log.w('check paln upload operate failed.')
            return False

        plan_file = '{0}\\{1}\\{1}_{2}_{3}.xml'.format(
                        plan_tool.LOCAL_PLAN_PATH, 
                        self.rnc.name, self.rnc.build,
                        time.strftime('%Y%m%d_%H%M%S', time.localtime())
                     )

        remote_plan = '/RUNNING/PLATMP/RNWPLANU.XML'
        ftp = crnc.cRncFtp(self.rnc.ip)
        rslt = ftp.download(remote_plan, plan_file)
        del ftp
        if not rslt:
            return False

        self.rnc.clear_omu_buffer(False)
        self.log.w('paln upload successful.')

        return plan_tool.format_xml(plan_file)

    def download_plan(self):
        self.log.w('select plan', end=' ')
        plan_file= self.obj.Dialog.FileOpenDialog(
                      title='Please select a RNWPlan', 
                      filter='xml Files (*.xml)|*.xml||'
                   )
        if not plan_file:
            self.log.w('canceled.', False)
            return False
        else:
            self.log.w(plan_file, False)
            if not os.path.isfile(plan_file):
                self.log.w('RNWPlan not exist!')
                return False

        self.rnc.clear_omu_buffer()
        if not self.wait_noraml_db_state():
            return False

        self.rnc.enter_omu()
        # Send task_id and plan_id for plan download
        mml = 'ZOS:*,*,523,,,,,D80D,,,10,{0},{1},{2}\r'.format(
                 TASK_ID_GLOBAL_DL, PLAN_ID_GLOBAL, TASK_ID_DUMMY
              )
        self.rnc.board_crt.send(mml)
        for times in range(MAX_RETRY_TIMES):
            if self.wait_ack_msg(PLAN_TASK_ID_REQ_ACK):
                break
            else:
                debug.sleep(10.0*(times+1))
        else:
            self.log.w('set plan task id failed.')
            return False

        # D7FE is 'rnw_plan_download_req_s' NEMU --> RAYMAN,
        # starts the RNW plan download operation in RNC DX.
        mml = 'ZOS:*,*,523,,,,,D7FE,,,{0},00\r'.format(TASK_ID_GLOBAL_DL);
        self.rnc.board_crt.send(mml)
        if not self.wait_ack_msg(PLAN_DL_ACK):
            self.log.w('plan download ack failed.')
            return False

        remote_plan = '/RUNNING/PLATMP/RNWPLAND.XML'
        ftp = crnc.cRncFtp(self.rnc.ip)
        rslt = ftp.upload(plan_file, remote_plan)
        del ftp
        if not rslt:
            return False

        # D7FC 'plan_file_ready_s'
        # This message indicates to that plan file downloading
        # to RNC disk is completed .
        mml = 'ZOS:*,*,523,01,,,,D7FC,,,00,00,00,00\r'
        self.rnc.board_crt.send(mml)
        if not self.wait_ack_msg(PLAN_DL_SCHEMA_VERSION):
            self.log.w('plan file is not ready.')
            return False

        # D01A 'rnw_plan_schema_version_ack_s' NEMU --> RAYMAN ,
        # it is an acknowledgement to the message rnw_plan_schema_version_s,
        # informing that plan operation may continue.
        mml = 'ZOS:*,*,523,1,,,,D01A,,,00,00,00,00\r'
        self.rnc.board_crt.send(mml)

        # receive first feedback and send feedback request to RAYMAN
        mml = 'ZOS:*,*,523,1,,,,B2D2,,,FF,FF,FF,FF\r'
        self.rnc.board_crt.send(mml)

        if not self.check_operate_result(PLAN_DL_END_MSG):
            self.log.w('plan rollback failed.')
            return False

        self.rnc.clear_omu_buffer(False)
        self.log.w('download plan successful.')
        return True

    def active_plan(self):
        self.log.w('start active plan.')
        self.rnc.clear_omu_buffer()
        if not self.wait_noraml_db_state():
            return False

        self.rnc.enter_omu()
        # Send task_id and plan_id for plan download
        mml = 'ZOS:*,*,523,,,,,D80D,,,11,{0},{1},{2}\r'.format(
                TASK_ID_GLOBAL_ACT, PLAN_ID_GLOBAL, TASK_ID_DUMMY
              )
        self.rnc.board_crt.send(mml)
        for times in range(MAX_RETRY_TIMES):
            if self.wait_ack_msg(PLAN_TASK_ID_REQ_ACK):
                break
            else:
                debug.sleep(10.0*(times+1))
        else:
            self.log.w('set plan task id failed.')
            return False

        # rnw_plan_activation_req_s RN7.0
        mml = 'ZOS:*,*,523,,,,,D7FA,,,{0},{1},02\r'.format(
                TASK_ID_GLOBAL_ACT, PLAN_ACT_MODE
              )
        self.rnc.board_crt.send(mml)
        if not self.wait_ack_msg(PLAN_ACT_ACK):
            self.log.w('paln activation act failed.')
            return False

        if not self.check_operate_result(PLAN_ACT_END_MSG):
            self.log.w('paln activation failed.')
            return False

        self.rnc.clear_omu_buffer(False)
        self.log.w('paln activation successful.')
        return True
